package sdiff.tests;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import smallChanges.lineNumberMapping.StatementMapper;
import smallChanges.statements.StatementMappingResultSet;

public class ReissConfig
{
    private Map<Integer, LineMapping> correctResultsMap=
        new TreeMap<Integer, LineMapping>();
    
    private int numVersions;
    private String filename;
    
    public Map<Integer,LineMapping> getResults() {
        return correctResultsMap;
    }
    
    /**
     * @deprecated
     */
    private static class ReissScoreAndXML
    {
        public final ReissScore score;
        public final String xml;
        public final Map<Integer,String> xmlMapping=new TreeMap<Integer,String>();
        
        public ReissScoreAndXML(ReissScore score, String xml) {
            this.score=score;
            this.xml=xml;
        }
    }
    
    public void scoreLineMappingForVersion(
            LineMapping mapping, 
            int versionNum,
            String method,
            ReissResultsCollection resultsCollection)
    {
        ReissScore score=new ReissScore();
        StringBuffer buf=new StringBuffer();
        ReissLocationMap locationMap=resultsCollection.getReissLocationMap(versionNum);
        
        // Get the correct results
        LineMapping correct=correctResultsMap.get(versionNum);
        // Make a copy of the correct results
        // we will remove these as we match them
        // anything left is a mismatch
        LineMapping temp=new LineMapping(correct);
        
        // Check changed lines of correct version
        // Make sure we matched those lines together
        for (Map.Entry<Integer, Set<Integer>> entry : 
            correct.getChanged().entrySet())
        {
            int lhs=entry.getKey();
            for (int rhs : entry.getValue()) {
                String location="<LOCATION ID=\"L_"+lhs+"\" ORIG=\""+lhs+"\" " +
                    "NEW=\""+rhs+"\">";
                buf.append(location+"\n");
                
                int rhsActual=mapping.findMatch(lhs, rhs);
                String methodLine="<METHOD ID=\""+method+"\" LINE=\""+rhsActual+"\"/>";
                buf.append(methodLine+"\n");
                
                locationMap.put(location, methodLine);
                
                if (rhs==rhsActual) {
                    // we found the line!
                    score.correctLine();
                    temp.remove(lhs,rhs);
                } else if (rhs > -1 && rhsActual > -1) {
                    // neither is a delete
                    // but we didn't match it accurately
                    // mismatch
                    score.mismatchedChange();
                } else if (rhs <= -1 && rhsActual > -1) {
                    // should have been a delete but wasn't
                    // spurious
                    score.spuriousDelete();
                } else if (rhs > -1 && rhsActual <= -1) {
                    // should have matched but was marked delete
                    // elim
                    score.eliminatedDelete();
                } else {
                    throw new RuntimeException("Should not be possible " +
                    		"lhs: "+lhs+", rhs: "+rhs+", rhsActual: " +rhsActual);
                }
                buf.append("</LOCATION>\n");
            }
        }
        
        // No need to check unchanged
        // correct results don't use unchanged

        resultsCollection.putScore(method, versionNum, score);
    }
    
    public ReissConfig(String filename)
    throws IOException, SAXException, ParserConfigurationException
    {
        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        Document doc = docBuilder.parse(new File(filename));
        // normalize text representation
        doc.getDocumentElement ().normalize();
        this.filename=((Element)doc.getElementsByTagName("TEST").item(0)).getAttribute("FILE");
        //System.out.println ("Root of doc: " +doc.getDocumentElement().getNodeName()); 

        NodeList versionList = doc.getElementsByTagName("VERSION");
        numVersions = versionList.getLength();
        //System.out.println("total versions: " + numVersions);
        
        for(int v=0; v<versionList.getLength() ; v++){
            Node versionNode = versionList.item(v);
            
            if(versionNode.getNodeType() == Node.ELEMENT_NODE){
                Element version=(Element)versionNode;
                int versionNum=Integer.parseInt(version.getAttribute("NUMBER"));

                Set<String> locationLines=new HashSet<String>();
                
                NodeList locationList=version.getElementsByTagName("LOCATION");
                LineMapping correctLineMap=new LineMapping();
                for (int i=0; i<locationList.getLength(); i++) {
                    Node loc=locationList.item(i);
                    if (loc.getNodeType()==Node.ELEMENT_NODE) {
                        Element location=(Element)loc;
                        
                        int origLoc=Integer.parseInt(location.getAttribute("ORIG"));
                        int newLoc=Integer.parseInt(location.getAttribute("NEW"));
                        // -1 for newLoc is a delete
                        if (newLoc==-1) {
                            correctLineMap.addDeleted(origLoc);
                        }
                        // put the line we just found
                        correctLineMap.addChanged(origLoc, newLoc);
                        
                        locationLines.add("<LOCATION ID=\"L_" +origLoc+"\" " +
                        		"ORIG=\""+origLoc+"\" NEW=\""+newLoc+"\">");
                        
                        // now check for alternate mappings
                        // alternate mappings happen when one line maps to many lines
                        // so for example:
                        // 10:    f(x, y, z)
                        // 
                        // might map to:
                        // 10:    f(x,
                        // 11:      y,
                        // 12:      z)
                        // 
                        // In this situation, line 10 in the first version maps to 
                        // lines 10,11,12 of the second version.
                        
                        // TODO: What about when a statement broken across 3 lines
                        // maps to 3 lines in the next version?  How do we score that?
                        NodeList alternates=location.getElementsByTagName("ALTERNATE");
                        if (alternates.getLength()>0) {
                            for (int j=0; j<alternates.getLength(); j++) {
                                Element e=(Element)alternates.item(j);
                                int altLoc=Integer.parseInt(e.getAttribute("NEW"));
                                correctLineMap.addChanged(origLoc, altLoc);
                            }
                        }
                    }
                }
                correctResultsMap.put(versionNum, correctLineMap);
           }
        }
    }
    
    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString()
    {
        StringBuffer buf=new StringBuffer();
        for (Map.Entry<Integer, LineMapping> entry :getResults().entrySet())
        {
            int versionNum=entry.getKey();
            buf.append("versionNum: " +versionNum+"\n");
            buf.append(entry.getValue().toString());
        }
        return buf.toString();
    }
    
    public void sDiffAndScoreResults(
            String configName,
            boolean useTokenEditDistance,
            boolean useMinEditDistance,
            boolean useProbable,
            boolean usePossible,
            String folder,
            String javaVersion,
            ReissResultsCollection resultsCollection)
    throws IOException, ParserConfigurationException, SAXException
    {
        resultsCollection.add(new ReissResults(configName));
        String filePrefix=filename.replace(".java", "");
        
        // Reiss Tests compare the first file with files [2..N]
        String leftFileStr=folder+File.separator+filePrefix+"_"+1+".java";
        for (int next=2; next<=numVersions; next++) {
            String rightFileStr=folder+File.separator+filePrefix+"_"+next+".java";
            
            // TODO refactor StatementMapper constructor so
            // that we can configure the mapper, pass it into this method
            // and then set the left/right files and java versions
            // inside this method
            StatementMapper mapper=new StatementMapper(leftFileStr, javaVersion, 
                    rightFileStr, javaVersion);
            if (useTokenEditDistance)
                mapper.setRunTokenEditDistance(useTokenEditDistance);
            else if (useMinEditDistance)
                mapper.setUseMinTokenOrTextEditDistance(useMinEditDistance);
            long start=System.currentTimeMillis();
            StatementMappingResultSet rs=mapper.runStatementMapper();
            mapper.setValidMappings(rs);
            // XXX: Need to set the other kind of mappings?
            mapper.setProbablePossibleMappings(rs);
            long time=System.currentTimeMillis()-start;
            scoreLineMappingForVersion(
                    new LineMapping(rs, useProbable, usePossible), 
                    next,
                    configName,
                    resultsCollection);
            resultsCollection.addTimingInfo(configName, next, time);
        }
        
    }
    
    public void ldiffAndScoreResults(
            LdiffLineMappings ldiffMappings,
            String folder,
            ReissResultsCollection resultsCollection)
    throws IOException, ParserConfigurationException, 
        SAXException, InterruptedException
    {
        resultsCollection.add(new ReissResults(ldiffMappings.getConfigName()));

        String filePrefix=filename.replace(".java", "");
        String leftFileStr=folder+File.separator+filePrefix+"_"+1+".java";
        for (int next=2; next<=numVersions; next++) {
            String rightFileStr=folder+File.separator+filePrefix+"_"+next+".java";
            
            long time=ldiffMappings.runLdiff(leftFileStr, rightFileStr);
            scoreLineMappingForVersion(
                    ldiffMappings.getLineMapping(), 
                    next,
                    ldiffMappings.getConfigName(),
                    resultsCollection);
            resultsCollection.addTimingInfo(ldiffMappings.getConfigName(), next, time);
        }
    }
    
    
    /**
     * @return the numVersions
     */
    public int getNumVersions() {
        return numVersions;
    }
    /**
     * @return the filename
     */
    public String getFilename() {
        return filename;
    }
}
